#!/usr/bin/env python
"""
PyLink-SR (Python Linked Software Radio System)
(c) Bob Cowdery (G3UKB) 2008

File: jack_connect.py
Date: July 2008

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

The author can be reached by email at

bob@g3ukb.co.uk

================================================================================
Module Description:

The dsp service.

"""

#-------------------------------------------------------------------------------
# System imports
import logging
import sys
import os
import subprocess
from time import sleep
from os import kill
import signal
from threading import Thread
import Pyro.core
import Pyro.naming
from Pyro.EventService.Clients import Subscriber
import Pyro.EventService

# Set the PYTHONPATH
# This is a repeat of what goes on in app_main.py
HOME = os.environ['PYLINK_HOME']
sys.path.append(HOME + 'conf')
import config_service as config
config.open_config()
sys.path = sys.path + config.python_path().rsplit(',')

# Application imports
import defs
import config_service as config
import logger_service as logger
import streams
import dsp
import jack_invoke
import jack_connect
import pyro_services

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class PylinkDspService:
    
    #---------------------------------------------------------------------------
    def __init__(self):
        
        self.state = defs.DSP_STOP
        self.proc = None
        
        Pyro.core.initServer()
        Pyro.config.PYRO_HOST = config.pyro_local_host()
        
        # We always need a local jack regardless of mode
        self.j_i = jack_invoke.JackInvoke()
        self.j_c = jack_connect.JackConnect()
        if config.mode() <> 'standalone':
            # We need to start remote services as well
            self.j_i_remote = Pyro.core.getProxyForURI("PYRONAME://jackinvoke")
            self.j_c_remote = Pyro.core.getProxyForURI("PYRONAME://jackconnect")
        
        #-----------------------------------------------------------------------
        # Start jack services
        if config.mode() == 'standalone':
            self.j_i.start_deamon()
        else:
            # Start local and remote jack services in the correct sequence
            # Start the master jackd (talks to the primary sound card)
            self.j_i_remote.start_deamon()
            sleep(1)
            # Start the local jackd with the net backend
            self.j_i.start_deamon()
            sleep(1)
            # Start the remote net client
            self.j_i_remote.start_net_client()
            sleep(1)
            # Start the local alsa out to the local sound card 
            self.j_i.start_alsa_out()
            
        sleep(1)
        
        #-----------------------------------------------------------------------
        # Initialise the dsp components
        # DSP Implementation
        # Get the event service
        self.es = Pyro.core.getProxyForURI("PYRONAME://"+Pyro.constants.EVENTSERVER_NAME)
        self.dsp_impl = dsp.Dsp(self.es)
     
    #---------------------------------------------------------------------------  
    def get_impl(self):
        return self.dsp_impl
    
    #---------------------------------------------------------------------------
    def get_es(self):
        return self.es
    
    #---------------------------------------------------------------------------
    # Run the dsp
    def run(self):
      
      # Loop until told to close
      while self.state <> defs.DSP_CLOSE:
        if self.state == defs.DSP_START:
            # Started, so do one block of samples
            streams.do_pass(self.dsp_impl)
        # Safe to do any updates now
        sleep(0.001)
        
      # Fall through when a close request received
      # Kill the jack processes
      self.j_i.terminate()
      if config.mode() <> 'standalone':
        self.j_i_remote.terminate()
    
    #---------------------------------------------------------------------------
    def start_dsp(self):
      
      self.j_c.connect()
      if config.mode() <> 'standalone':
        self.j_c_remote.connect()
      streams.init()
      self.state = defs.DSP_START
    
    #---------------------------------------------------------------------------
    def stop_dsp(self):
      
      self.state = defs.DSP_STOP
      sleep(1)
      self.j_c.disconnect()
      if config.mode() <> 'standalone':
        self.j_c_remote.disconnect()
      
    #---------------------------------------------------------------------------
    def close_dsp(self):
      
      self.state = defs.DSP_CLOSE
    
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class DspUpdate(Subscriber):
    
    #---------------------------------------------------------------------------
    def __init__(self, service):
        Subscriber.__init__(self)
        self.setThreading(0)
        self.dsp_impl = service.get_impl()

    #---------------------------------------------------------------------------
    def event(self, event):
        # Wait for messages
        # Call the appropriate method        
        #print 'Received ', event.msg
        func, args = event.msg
        if func == 'start_dsp':
            service.start_dsp()
        elif func == 'stop_dsp':
            service.stop_dsp()
        elif func == 'close_dsp':
            service.close_dsp()
            self.abort()
        else:
            bound_func = getattr(self.dsp_impl, func)
            bound_func(args)

#------------------------------------------------------------------------------- 
class EventListener(Thread):
    
    #---------------------------------------------------------------------------
    def __init__(self, service):
        Thread.__init__(self)
        self.service = service
    
    #---------------------------------------------------------------------------    
    def run(self):
        global glogger
        Pyro.core.initServer()
        listener = DspUpdate(self.service)
        listener.subscribeMatch("dsp_events")
        glogger.info('PyLink (DSP Service) listening...')
        listener.listen()
        glogger.info('PyLink (DSP Service) listener exiting..')
            
#-------------------------------------------------------------------------------
#
# Start from the command line
#
if __name__ == "__main__":
    
    global glogger
    
    logger.init_logger()
    glogger = logging.getLogger('pylink.logger')

    # Create the service
    service = PylinkDspService()
    # Start the event listener thread
    EventListener(service).start()

    # Start main loop
    glogger.info('PyLink-SR (DSP Service) running..')
    service.run()
    glogger.info('PyLink (DSP Service) closing..')

